Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LibWeb+LibCrypto: Add Ed448 support in WebCryptoAPI #3009

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

devgianlu
Copy link
Contributor

Make use of the recently merged OpenSSL PR to implement Ed488 quick and easy.

Apart from implementing Ed448, I've also started defining some macros to aid development of code based on OpenSSL (see OPENSSL_TRY and OPENSSL_TRY_PTR).

The implemention is a bit different from the other curves obviously and now all the methods could be static. I have evaluated some possibilities:

  • We preallocate common OpenSSL structures when creating the class -> not ideal because then we would need OwnPtrs everywhere
  • We restructure the class to behave like RSA and require to initialize it with the keys instead of passing them to the methods
  • We make all methods static and make the class just an utility container

I think the decision can wait based on how the implementation of other things using OpenSSL goes, but wanted to share.

@devgianlu devgianlu requested a review from alimpfard as a code owner December 22, 2024 18:34
ErrorOr<ByteBuffer> Ed448::generate_private_key()
{
auto* key = OPENSSL_TRY_PTR(EVP_PKEY_Q_keygen(nullptr, nullptr, "ED448"));
ArmedScopeGuard const free_key { [&] { EVP_PKEY_free(key); } };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you never disarm the scope guard, you can use ScopeGuard instead.

Comment on lines 15 to 16
size_t key_size() { return 57; }
size_t signature_size() { return 114; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these member functions should at least be const.

size_t key_size() const { return 57; }

if not constexpr as well

constexpr size_t key_size() const { return 57; }

ErrorOr<bool> Ed448::verify(ReadonlyBytes public_key, ReadonlyBytes signature, ReadonlyBytes message, ReadonlyBytes context)
{
auto* key = OPENSSL_TRY_PTR(EVP_PKEY_new_raw_public_key_ex(nullptr, "ED448", nullptr, public_key.data(), public_key.size()));
ArmedScopeGuard const free_key { [&] { EVP_PKEY_free(key); } };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of using all these scope guards, perhaps we should invent some OpenSSL wrappers?

class OpenSSLPrivateKey {
public:
    ErrorOr<OpenSSLPrivateKey> create(StringView name, ReadonlyBytes public_key);
    ~OpenSSLPrivateKey();
    
    EVP_PKEY* ptr() { return m_ptr; }
private:
    OpenSSLPrivateKey(EVP_PKEY* ptr) : m_ptr(ptr) {};
    
    EVP_PKEY* m_ptr { nullptr }; 
};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, at least for the most common types, I'll protorype something

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 to me, a one-off scope guard is a handy tool, but using several identical scope guards in every method of a class screams "add some RAII"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really not sure about TRY(OpenSSL_PKEY::create(EVP_PKEY_Q_keygen, nullptr, nullptr, "ED448"));, does it make any sense?

Copy link
Contributor Author

@devgianlu devgianlu Dec 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that was dumb, should make more sense now

@ADKaster
Copy link
Member

After this goes in, a comment on WICG/webcrypto-secure-curves#20 seems appropriate

@devgianlu devgianlu force-pushed the ed448 branch 3 times, most recently from 1a3dde2 to fc1b5c5 Compare December 23, 2024 20:18

namespace Crypto::Curves {

ErrorOr<ByteBuffer> Ed448::generate_private_key()
Copy link
Contributor

@rmg-x rmg-x Dec 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-maintainer take: I think we should avoid fallible returns for things that most likely won't fail. Only because if they do fail, it's either us using the API incorrectly or something completely out of our control like a memory allocation failure. Failing fast in those scenarios seems better than bubbling it up to callers.

Slight bonus is that call sites of these functions become a little less noisy. Anyway, this could just return ByteBuffer and MUST(...) any fallible calls it makes IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I am of the opposite idea: since error handling comes at a little cost we should try to cover every case to preserve uniformity and future proofing.

Comment on lines 22 to 23
auto* err_message = ERR_error_string(err, nullptr); \
return Error::from_string_view(StringView { err_message, strlen(err_message) }); \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is safe to do, IIRC ERR_error_string will overwrite the static buffer on every call?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, I should make a new buffer beforehand.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, not sure if there's a better way to make the buffer and pass it around.

VERIFY(err); \
auto err_message = TRY(ByteBuffer::create_zeroed(256)); \
ERR_error_string_n(err, reinterpret_cast<char*>(err_message.data()), 256); \
return Error::from_string_view(StringView { err_message.data(), strlen(reinterpret_cast<char*>(err_message.data())) }); \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What you've written is always a use after free. AK::Error cannot own a string. If you want an owned string, you need to use a custom error type in ErrorOr<T, E>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mhh, I guess we would need something like OpenSSLError which would then escape up to whoever is making the call inhibiting usage of TRY along the way unless ErrorOr<U, OpenSSLError> is used.

I woundn't want to have

ErrorOr<ByteBuffer, OpenSSLError> Ed448::sign(ReadonlyBytes private_key, ReadonlyBytes message, ReadonlyBytes context)

since OpenSSL should be an internal detail. Do you reckon any other way to handle this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just suggest an enum or custom error strings

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have opted for the simplest solution right now: create an error via string literal and log the error. Another option could be to provide an error literal with the macro.

Add a couple of macros to aid error handling with OpenSSL and some RAII
classes that manage the lifetime of some OpenSSL objects.
Implement the Ed448 curve for signing and verifying using OpenSSL.

The methods could be all made static, but all other curves are not.
I think this is material for further refactoring.
Add full support for Ed448 and import relevant tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants